package syndicate.gangsters;

import syndicate.gi.*;
import java.util.List;

/**
 * Realization of the gangster with following priority of turn variants:
 * earn money, bear a baby, attack, move somewhere else.
 * (*) after baby birth females give some money to their mates to recover mates'
 * money.
 *
 * The realization of moving is following: every gangster always moves
 * in one direction according to its id. For males and females
 * id counters are different. So we can assume that when
 * the available money in the location is over, in every direction
 * some gangsters will go and among them both males and females will be.
 *
 * Features: heavy and slow => almost no money consumption for moving
 *
 * Especially good at duels!
 *
 * @author: Eugene Larchick
 */

public class SmartGangster implements Gangster {

    // unchangeable parameters for all ComplexGangsters
    public static final float AUTHORITY = 4f;
    public static final float SPEED = 1f;
    public static final String NAME = "complex";
    public static final String SYNDICATE_NAME = "Smart";

    // determines how much of females were born so far
    public static int femaleCount = 0;


    // determines how much of males were born so far
    public static int maleCount = 0;

    // our SmartGangster's id
    // (different for two different gangsters of the same sex)
    private int id = 0;

    // the gangster's parameters
    private GangsterParams params;

    // the gangster's global id
    private Integer globalID;

    // the gangster's sex
    private GangsterSex sex;

    // the gangster's syndicate
    private Object syndicate;

    // current money
    private float money;

    // mate id (is used only for males to know
    // to which female he should give money after bearing a baby)
    private Integer mateID;

    // determines if the gangster got a baby on the previous move
    // (is used only for females to know the moment to give money to the mate)
    private boolean afterBorn;

    // this method is invoked once per game,
    // to make sure all static are properly inited
    public void reinit(UserGameInfo info) {
        femaleCount = 0;
        maleCount = 0;
    }

    // returns gangster's name
    public String getName() {
        return NAME;
    }

    // returns the gangster syndicate name (player name)
    public String getSyndicateName() {
        return SYNDICATE_NAME;
    }

    // returns gangster's params
    public GangsterParams getParams(GangsterSex sex) {
        if (params == null) {
            params = new GangsterParams(AUTHORITY, SPEED, null, null);
        }
        return params;
    }

    // acording to the kind of event we got does some things
    public void processEvent(GangsterInterface gi, Event e) {
        switch (e.kind()) {
            case EVENT_BORN:
                init(gi);
                defineID();
                break;
            case EVENT_BECAME_PARENT:
                afterBorn = true;
        }
    }

    // defines SmartGangster id
    private void defineID() {
        if (sex == GangsterSex.Male) {
            maleCount++;
            id = maleCount;
        } else {
            femaleCount++;
            id = femaleCount;
        }
    }

    // fills the fields with info from GangsterInteraface
    private void init(GangsterInterface gi) {
        params = new GangsterParams(AUTHORITY, SPEED, null, null);
        globalID = gi.getId(this);
        sex = gi.getSex(this);
        syndicate = gi.getSyndicate(this, globalID);
    }

    /*
     * this one is the essence of your gangster - it's invoked once a turn
     * and lets your gangster do something
     * */
    public Event makeTurn(GangsterInterface gi) {
        Event event = null;
        PointInfo pi = gi.getPointInfo(this);
        money = gi.getMoney(this);

        float moneyAvailable = pi.getCount(this);
        int targetId;

        // if gangster should give money to the mate
        if ((event = mayBeGiveMoneyToMate()) != null) {
            return event;
        }

        // if gangster should earn money
        if ((event = maybeEarnMoney(gi)) != null) {
            return event;
        }

        // if gangster should propose someone to bear a baby
        if ((event = mayBeBorn(gi)) != null) {
            return event;
        }

        // if gangster should attack someone
        if ((event = mayBeAttack(gi)) != null) {
            return event;
        }

        // if none of previous variants then move
        return new Event(EventKind.ACTION_MOVE_TO, whereToGo(gi));
    }

    // if we can attack some other gangster then attack
    private Event mayBeAttack(GangsterInterface gi) {
        Integer targetId;

        if ((targetId = getGangsterToAttack(gi)) != null) {
            return new Event(EventKind.ACTION_MAKE_TROUBLE, targetId);
        }

        return null;
    }

    // if we can bear a baby and found someone we want to have baby with
    // then try to bear a baby
    private Event mayBeBorn(GangsterInterface gi) {
        Integer mate;

        if ((money > Constants.K_toborn * AUTHORITY)
                && ((mate = getGangsterToBearBaby(gi)) != null)) {
            // remember mate
            params.id = mate;

            // determine the sex of the baby
            params.sex = (maleCount <= femaleCount) ? GangsterSex.Male : GangsterSex.Female;
            return new Event(EventKind.ACTION_BORN, params);
        }

        return null;
    }

    // if gangster needs money and and can earn some then earn
    private Event maybeEarnMoney(GangsterInterface gi) {
        float moneyAvailable = gi.getPointInfo(this).getCount(this);

        if ((moneyAvailable >= Constants.K_bite * AUTHORITY)
                && (money < (1 - Constants.K_bite) * AUTHORITY)) {
            return new Event(EventKind.ACTION_EARN_MONEY, AUTHORITY);
        }

        return null;
    }

    // if sex is female and our mate born a baby on the previous move
    // let's support him and give him money:)
    private Event mayBeGiveMoneyToMate() {
        if (afterBorn && sex == GangsterSex.Female) {
            afterBorn = false;
            return new Event(EventKind.ACTION_GIVE_MONEY, mateID);
        }
        return null;
    }

    /*
    * determins if there are some of our gangsters in the current location
    * and finds the best partner as the gangster with the least differet id from ours
    * this rases the possibility of coincidence
    * returns null if there are no gangsters of the other sex here
    * */
    private Integer getGangsterToBearBaby(GangsterInterface gi) {
        Integer[] entities = gi.getPointInfo(this).getEntities(this, null);

        if (entities == null) {
            return null;
        }

        Integer bestDifference = Integer.MAX_VALUE;
        Integer idOfTheBest = null;

        for (int i = 0; i < entities.length; i++) {
            Integer entity = entities[i];

            if ((syndicate == gi.getSyndicate(this, entity)
                    && (sex != gi.getSex(entity)))) {
                if (Math.abs(globalID - entity) < bestDifference) {
                    bestDifference = Math.abs(globalID - entity);
                    idOfTheBest = entity;
                }
            }
        }
        mateID = idOfTheBest;
        return idOfTheBest;
    }

    /*
    * determines whether we have some opponents near and if we should
    * attack one of them returns its id
    * returns null if there is nobody we should attack
    * */
    private Integer getGangsterToAttack(GangsterInterface gi) {
        Integer[] entities = gi.getPointInfo(this).getEntities(this, null);

        if (entities == null) {
            return null;
        }

        float opponentMoneyLoss = Constants.K_fight * AUTHORITY;
        float ourMoneyLoss = 0f;

        for (int i = 0; i < entities.length; i++) {
            Integer entity = entities[i];

            // if entity is not oppennent continue
            if (syndicate == gi.getSyndicate(this, entity)) {
                continue;
            }

            ourMoneyLoss = Constants.K_fightcost * AUTHORITY +
                    Constants.K_retaliate * gi.getMoney(this, entity);
            // if the damage to the entity is
            // more then our loss of money then attack
            if (opponentMoneyLoss > ourMoneyLoss) {
                return entity;
            }
        }
        return null;
    }

    /*
    * determines to which location we should go
    * */
    private Location whereToGo(GangsterInterface gi) {
        List<PointInfo> neighboursList = gi.getNeighbourInfo(this);

        // if we need to earn money
        if (money < (1 - Constants.K_bite) * AUTHORITY) {
            for (PointInfo i : neighboursList) {
                // simply if the point has enough money to get a baby
                // then choose its location as the best to go
                if ((i.getCount(this) >= Constants.K_toborn * AUTHORITY)) {
                    return i.getLocation();
                }
            }
        }

        // else choose the one of neighbouring locations acording to SmartGangster id
        return neighboursList.get(id % neighboursList.size()).getLocation();
    }
}

